package tech.yaog.usbcamera;

import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.hardware.usb.UsbDevice;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;

import java.nio.ByteBuffer;

import tech.yaog.hardware.usb.USBMonitor;
import tech.yaog.hardware.usb.UVCCamera;

public class CameraService extends Service {

    public static final String EXTRA_VID = "VID";
    public static final String EXTRA_PID = "PID";
    public static final String EXTRA_WIDTH = "WIDTH";
    public static final String EXTRA_HEIGHT = "HEIGHT";

    private static final String TAG = CameraService.class.getName();
    private static final int WIDTH_DEFAULT = 1920;
    private static final int HEIGHT_DEFAULT = 1080;

    public interface OnCaptureResult {
        void onCaptureSuccess(Bitmap bitmap);
    }

    public CameraService() {
    }

    private USBMonitor usbMonitor;
    private int VID, PID;
    private Looper looper;
    private UVCCamera uvcCamera;
    private final Object captureLock = new Object();
    private boolean capture;
    private int previewWidth, previewHeight;
    private OnCaptureResult onCaptureResult;
    private PreviewDisplay previewDisplay;

    private void stopService() {
        looper.quit();
        closeUsbMonitor();
        closeUVC();
    }

    private void closeUsbMonitor() {
        if (usbMonitor != null) {
            usbMonitor.unregister();
            usbMonitor.destroy();
            usbMonitor = null;
        }
    }

    private void closeUVC() {
        if (uvcCamera != null) {
            uvcCamera.close();
            uvcCamera = null;
        }
    }

    private void openUVC(USBMonitor.UsbControlBlock controlBlock) {
        uvcCamera = new UVCCamera();
        uvcCamera.open(controlBlock);

        uvcCamera.setFrameCallback(frame -> {
            boolean toHandle;
            synchronized (captureLock) {
                toHandle = capture;
            }
            if (toHandle) {
                synchronized (captureLock) {
                    capture = false;
                }
                int len = frame.capacity();
                final byte[] rgb = new byte[len];
                frame.get(rgb);
                Bitmap bitmap = Bitmap.createBitmap(previewWidth, previewHeight, Bitmap.Config.RGB_565);
                bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(rgb));
                if (onCaptureResult != null) {
                    onCaptureResult.onCaptureSuccess(bitmap);
                    onCaptureResult = null;
                }
            }
        }, UVCCamera.PIXEL_FORMAT_RGB565);

        uvcCamera.setPreviewSize(previewWidth, previewHeight, UVCCamera.FRAME_FORMAT_MJPEG, UVCCamera.DEFAULT_BANDWIDTH);
        uvcCamera.startPreview();
        if (previewDisplay != null) {
            if (previewDisplay.isSurface()) {
                uvcCamera.setPreviewDisplay(previewDisplay.getSurface());
            }
            else {
                uvcCamera.setPreviewDisplay(previewDisplay.getSurfaceHolder());
            }
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        VID = intent.getIntExtra(EXTRA_VID, 0xFFFF);
        PID = intent.getIntExtra(EXTRA_PID, 0xFFFF);
        previewWidth = intent.getIntExtra(EXTRA_WIDTH, WIDTH_DEFAULT);
        previewHeight = intent.getIntExtra(EXTRA_HEIGHT, HEIGHT_DEFAULT);

        Handler handler = new Handler();
        Thread thread = new Thread(() -> {
            Looper.prepare();
            looper = Looper.myLooper();

            Handler uvcHandler = new Handler(looper);
            handler.post(() -> {
                usbMonitor = new USBMonitor(this, uvcHandler, new USBMonitor.OnDeviceConnectListener() {
                    @Override
                    public void onAttach(UsbDevice device) {
                        Log.d(TAG, "USB Device Attached: "+device.getDeviceName());
                        if (device.getVendorId() == VID && device.getProductId() == PID) {
                            Log.d(TAG, "New found");
                            usbMonitor.requestPermission(device);
                        }
                    }

                    @Override
                    public void onDettach(UsbDevice device) {

                    }

                    @Override
                    public void onConnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) {
                        openUVC(ctrlBlock);
                    }

                    @Override
                    public void onDisconnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock) {
                        if (uvcCamera != null) {
                            closeUVC();
                        }
                    }

                    @Override
                    public void onCancel(UsbDevice device) {

                    }
                });
                usbMonitor.register();
            });
            Looper.loop();
        });
        thread.setName("Camera Main Looper");
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
        return START_REDELIVER_INTENT;
    }

    public void setPreviewDisplay(Surface display) {
        previewDisplay = new PreviewDisplay(display);
        if (uvcCamera != null) {
            uvcCamera.setPreviewDisplay(display);
        }
    }

    public void setPreviewDisplay(SurfaceHolder surfaceHolder) {
        previewDisplay = new PreviewDisplay(surfaceHolder);
        if (uvcCamera != null) {
            uvcCamera.setPreviewDisplay(surfaceHolder);
        }
    }

    public void doCapture(OnCaptureResult onCaptureResult) {
        this.onCaptureResult = onCaptureResult;
        synchronized (captureLock) {
            capture = true;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new CameraBinder();
    }

    public class CameraBinder extends Binder {
        public CameraService getService() {
            return CameraService.this;
        }
    }

    private class PreviewDisplay {
        private Surface surface;
        private SurfaceHolder surfaceHolder;
        private boolean isSurface = true;

        PreviewDisplay(Surface surface) {
            this.surface = surface;
            isSurface = true;
        }

        PreviewDisplay(SurfaceHolder surfaceHolder) {
            this.surfaceHolder = surfaceHolder;
            isSurface = false;
        }

        Surface getSurface() {
            return surface;
        }

        SurfaceHolder getSurfaceHolder() {
            return surfaceHolder;
        }

        boolean isSurface() {
            return isSurface;
        }
    }
}
